#ifndef _COMPILATION_FUNCTION_H_
#define _COMPILATION_FUNCTION_H_

#include <compilation/Forward.h>

#include <iostream>
#include <vector>

#include <compilation/instruction/Type.h>
#include <compilation/instruction/Instruction.h>


namespace compilation
{

class FunctionSignature
{
private:

  std::vector<VirtualType*> m_parameterType;
  std::vector<std::string> m_parameterNames;
  std::auto_ptr<VirtualType> m_resultType;

public:

  FunctionSignature(void) {}

  ~FunctionSignature(void)
  {
    for (std::vector<VirtualType*>::iterator iIter = m_parameterType.begin();
         iIter != m_parameterType.end(); ++iIter)
      if (*iIter) delete (*iIter);
    m_parameterType.clear();
  }

  void addNewParameter(const std::string& parameterName, VirtualType* type)
  {
    m_parameterType.push_back(type);
    m_parameterNames.push_back(parameterName);
  }

  void setResult(VirtualType* type)
  { m_resultType.reset(type); }

  int size(void) const
  { return m_parameterType.size(); }

  const VirtualType& getTypeParameter(int uIndex) const
  { return *m_parameterType[uIndex]; }

  const VirtualType& getResultType(void) const
  { return *m_resultType; }

  bool findParameters(const std::string& name, int& local) const
  {
    bool fResult = false;
    int uSize = m_parameterNames.size();

    for (int uIndex = 0; uIndex < uSize; ++uIndex)
      if (m_parameterNames[uIndex] == name)
      {
        local = uIndex;
        return true;
      }

    return fResult;
  }

  friend class Function;
};

class Function
{
private:

  Scope* m_globalScope;
  FunctionSignature m_signature;
  std::string m_name;
  std::vector<VirtualInstruction*> m_instructions;

public:

  Function(void)
    : m_globalScope(nullptr)  {}

  Function(const std::string& name)
    : m_globalScope(nullptr), m_name(name)  {}

  Function(const std::string& name, Scope& globalScope)
    :  m_globalScope(&globalScope), m_name(name)  {}

  Function(const Function& source)
    :  m_globalScope(source.m_globalScope), m_name(source.m_name) {}

  ~Function(void)
  {
    for (std::vector<VirtualInstruction*>::iterator iIter = m_instructions.begin();
         iIter != m_instructions.end(); ++iIter)
      if (*iIter) delete (*iIter);
    m_instructions.clear();
  }

  Function& operator=(const Function& source)
  {
    m_globalScope = source.m_globalScope;
    m_name = source.m_name;
    return *this;
  }

  void print(std::ostream& out, std::function<void(void)> indent) const
  {
    out << "function " << m_name << '\n';
    for (std::vector<VirtualInstruction*>::const_iterator iIter =
         m_instructions.begin();
         iIter != m_instructions.end(); ++iIter)
      (*iIter)->print(out, indent);
  }

  bool operator<(const Function& source) const
  { return m_name < source.m_name; }

  bool operator<=(const Function& source) const
  { return m_name <= source.m_name; }

  bool operator>=(const Function& source) const
  { return m_name >= source.m_name; }

  bool operator>(const Function& source) const
  { return m_name > source.m_name; }

  bool operator==(const Function& source) const
  { return m_name == source.m_name; }

  bool operator!=(const Function& source) const
  { return m_name != source.m_name; }

  FunctionSignature& signature(void)
  { return m_signature; }

  const FunctionSignature& signature(void) const
  { return m_signature; }

  const std::string& getName(void) const
  { return m_name; }

  bool findParameters(const std::string& name, int& local) const
  { return signature().findParameters(name, local); }

  VirtualInstruction& getFirstInstruction(void) const
  { return *m_instructions[0]; }

  void addNewInstructionAfter(
      VirtualInstruction* newInstruction,
      VirtualInstruction& previous)
  {
    newInstruction->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(newInstruction);
    previous.connectTo(*newInstruction);
  }

  void addFirstInstruction(
      VirtualInstruction* newInstruction)
  {
    assert(m_instructions.empty());
    newInstruction->setRegistrationIndex(0);
    m_instructions.push_back(newInstruction);
  }

  void addNewInstruction(
      VirtualInstruction* newInstruction)
  {
    newInstruction->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(newInstruction);
  }

  void insertNewInstructionAfter(
      VirtualInstruction* newInstruction,
      VirtualInstruction& previous)
  {
    newInstruction->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(newInstruction);

    VirtualInstruction* next = previous.disconnectNext();
    previous.connectTo(*newInstruction);
    if (next) newInstruction->connectTo(*next);
  }

  GotoInstruction& setFirst(void)
  {
    assert(m_instructions.empty());
    GotoInstruction* result = new GotoInstruction();
    result->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(result);
    return *result;
  }

  GotoInstruction& setThen(IfInstruction& ifInstruction)
  {
    GotoInstruction* thenPoint = new GotoInstruction();
    thenPoint->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(thenPoint);
    ifInstruction.connectToThen(*thenPoint);
    return *thenPoint;
  }

  GotoInstruction& setElse(IfInstruction& ifInstruction)
  {
    GotoInstruction* elsePoint = new GotoInstruction();
    elsePoint->setRegistrationIndex(m_instructions.size());
    m_instructions.push_back(elsePoint);
    ifInstruction.connectToElse(*elsePoint);
    return *elsePoint;
  }

  const VirtualType& getTypeGlobal(int uIndex)
  { return m_globalScope->getType(uIndex); }

  const Scope& globalScope() const
  { assert(m_globalScope); return *m_globalScope; }

  void setDominationFrontier(void);
};

} // compilation

#endif // _COMPILATION_FUNCTION_H_
